<?php

1->消息总线在微服务中的应用
   -) '遗留问题'
       Spring Cloud中的分布式配置组件Config, 每个服务节点可以从Config Server拉取外部配置信息.
       但是似乎还有一个悬而未决的问题: 那就是当服务节点数量非常庞大的时候, 我们不可能一台一台服务器挨个去手工触发刷新,
       这时候就需要一个可以号令武林的角色出场, 由它代替我们做'批量刷新'的事儿.

   -) '谁能堪此大任?'
#      武林至尊 宝刀屠龙 号令武林 莫敢不从
       BUS-消息总线, 从这个'总'字就可以看出身份地位不一般, 它代理了这个号令武林的角色, 将消息变更发送给所有的服务节点.

       在微服务架构的系统中，通常我们会使用消息代理来构建一个Topic，让所有服务节点监听这个主题，当生产者向Topic中发送变更的时候，
       这个主题产生的消息会被所有实例所消费，这就是消息总线的工作模式，也就是我们熟悉的'发布-订阅'模型。

       其实广义的消息总线'不单'指代这种'发布-订阅'的模式，也可以代指分布式服务间进行通信、消息分发的'单播'模式，
       甚至有的公司既不使用HTTP也不用RPC来构建微服务，完全靠消息总线来做服务调用。比如银行的一些老系统就是采用总线型架构，在不同服务节点之间做消息分发。

       Spring Cloud中BUS的职责范围就相对小了很多，因为还有一个Stream组件代理了大部分的消息中间件通信服务，
       因此BUS在实际应用中'大多'是为了应对'消息广播'的场景, 比如和Config一同搭配使用推送配置信息。

   -) '总线架构完整流程'
                                        +----------+                  +----------+
                                        |  Config  | ---------------> |  GitHub  |
                                        +----+-----+                  +----------+
                                             |
                            +----------------+----------------+
                            |                |                |
                           服务             服务              服务
                            +----------------+----------------+
                                             |
                                        +----+---+            +----------+
                Refresh请求 -----------> |   BUS  | <========> | MQ/Kafka |
                                        +--------+            +----------+
        . MQ/Kafka: BUS是一个调用封装，它背后还是需要依赖消息中间件来完成底层的消息分发，实际项目中最常用的两个中间件分别是RabbitMQ和Kafka.
        . BUS: 作为对接上游应用和下游中间件系统的中间层，当接到刷新请求的时候，通知底层中间件向所有服务节点推送消息.
        . Refresh: 在Config章节中我们通过Refresh请求来刷新配置，那么对于总线式架构的Refresh请求来说，有两个需要解决的问题:
          - 谁来发起变更 - 是由服务节点, 还是由Config Server发起变更请求?
          - 何时发起变更 - 是手工发起变更? 还是每次GitHub改动完成后自动推送?

   -) '那位大人 - Stream'
       BUS底层依赖了Stream来广播消息，真正实现与消息代理进行交互的实际上是Stream，它才是站在BUS背后的男人。

   -) '全家桶'
       Spring Cloud各个组件之间常有相互依赖的关系，比如Feign和Ribbon还有Hystrix搭配，Config和BUS，BUS和Stream，
       整个Spring Cloud各组件配合的天衣无缝浑然一体，秉承了Spring家族“全家桶”式的设计思想。回想过去十多年来层出不穷的开源框架，
       能走到今天依然在Java企业级开发领域呼风唤雨的也只有Spring了，从最初的IOC+AOP，到MVC+全家桶组件库，再到SpringBoot，现在又是SpringCloud，
       Spring一直是Java领域的弄潮儿，这种生命力和创新精神真的是相当厉害。如果对开源领域感兴趣的话，建议持续关注Spring的动态以及正在孵化的项目，
       也许下一个爆款框架就从Spring中诞生 ^=^.



2->BUS(没时间解释了, 快上车!!!)
   -) '标签'
                                     轻量级 (确实是轻量级, 底层依赖Stream组件)
                                       |
                                  +----+----+
       消息广播 (基于'发布/订阅') ---+ 消息总线  +--- 自定义消息 (能做的不只有刷新)
                                  +----+----+
                                       |
                                  无缝集成Config

   -) '场景'
       配置变更通知(只是通知, 并不会送货上门) <-------- BUS --------> 自定义消息广播


3->BUS体系结构解析
   -) '消息广播.发布/订阅'
       不知道大家有没有偶遇明星的经历，有一次在外滩一个酒店开会，隔壁是《那年花开月正圆的》的发布会+粉丝见面会。所有主演全部到场的那一刹那，
       粉丝像打了半斤鸡血一样瞬间爆发，那发自灵魂深处的呐喊差点没把酒店掀翻。老师还以为隔壁发生了恐怖袭击，
       正往桌子底下钻的时候隐约听见某个和芈月有关的明星名字，这才回过神来.

       类比追星:
       . 主办方: 作为消息发布者，是一个中间层，一头对接粉丝另一头对接明星，专门发布'明星到来'的消息.
       . 粉丝团: 作为事件监听者，时刻监听明星们的动态，从主办方获取”明星到来“的消息，然后作出响应.
       . 明星: 作为事件主体，”明星到来“作为一个事件，由主办方发布出去，同时获得关注者的响应.

       上面这个模式对应到Bus的体系结构中来说:
       Bus: 就是这个主办方, 负责发布消息;
       各个监听消息的服务节点: 就是粉丝团, 时刻等待着响应新消息;
       配置变更: 就相当于是一个'明星到来'的事件，由Bus广播发布到所有服务节点.

   -) '事件的结构'
       在Bus中配置刷新的事件类是RefreshRemoteApplicationEvent，在Bus的规范下，所有广播事件都包含了三个维度的信息:
       . Source - 实体消息: 这是一个必填信息，它可以是一个自定义并且能够被'序列化/反序列化'的POJO对象，它包含了一个事件的想要传达的信息.
       . Original Service - 消息来源方: 通常是事件发布方的机器ID，或者AppId等.
       . Destination Service - 目标机器: Bus会根据Destination Service指定的过滤条件(比如服务名，端口等), 只让指定的监听者响应事件.

   -) '消息发布者'
       我们所有的“事件”都是通过Bus来发布的，Bus默认提供了两个Endpoint作为消息发布者:
       . bus-env
         在本地发布EnvironmentChangeRemoteApplicationEvent事件，表示一个远程环境变更事件。进一步查看这个事件的内容，我们发现其中包含了一个
         'Map<String, String>'属性, 事件监听者接收到这个事件之后，会将事件中的Map添加到Spring环境变量中(由Spring Cloud的EnvironmentManager负责具体处理),
         从而达到修改环境变量的目的.

       . bus-refresh
         发布RefreshRemoteApplicationEvent事件，表示一个远程配置刷新事件，这个事件会触发@RefreshScope注解所修饰的Java类中属性的刷新
         (这里顺道复习一下Config的知识，@RefreshScope修饰的类可以在运行期更改属性).

       以上两个Endpoint就是Bus通过"/actuator"服务对外提供出来的.

   -) '消息监听者'
       Bus中默认创建了两个消息监听器，分别对应上面的两个消息发布Endpoints:
       . EnvironmentChangeListener: 用来监听远程环境变更事件, 将事件中传递的环境变量挨个加入到本地上下文中;
       . RefreshListener: 用来监听远程配置刷新事件，其底层是通过触发另外的两个Spring Cloud的事件EnvironmentChangeEvent和RefreshScopeRefreshedEvent,
                          最终实现属性刷新.



4->Bus接入方式
   前面我们了解了Bus的工作方式，在动手改造配置中心之前，我们先来了解一下Bus有哪些接入方式.
   Spring的组件一向是以一种插件式的方式提供功能，将组件自身和我们项目中的业务代码隔离，使得我们更换组件的成本可以降到最低。
   Spring Cloud Bus也不例外，它的底层消息组件非常容易替换，替换过程不需要对业务代码引入任何变更。Bus就像一道隔离了底层消息组件和业务应用的中间层，
   比如我们从RabbitMQ切换为Kafka的时候，只需要做两件事就好了:
   . 在项目pom中替换依赖组件;
   . 更改配置文件里的连接信息;

   -) '接入RabbitMQ'
       点进去查看这个依赖的pom，你会发现它依赖了'spring-cloud-starter-stream-rabbit'，
       也就是说Stream组件才是真正被用来发送广播消息到RabbitMQ的，Bus这里只是帮我们封装了整个消息的发布和监听动作。
       . ['pom']
           <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-bus-amqp</artifactId>
           </dependency>

       . ['配置文件']
           spring:
             rabbitmq:
               host: localhost
               port: 5672
               username: guest
               password: guest

   -) '接入Kafka'
       . ['pom']
           <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-bus-kafka</artifactId>
           </dependency>

       . ['kafka配置']
           如果大家的Kafka和ZooKeeper都运行在本地，并且采用了默认配置，那么不需要做任何额外的配置，就可以直接使用。
           但是在生产环境中往往Kafka和ZooKeeper会部署在不同的环境，所以就需要做一些额外配置:
           +----------------------------------------------------------------------------+
           |  属性  |                                                              含义  |
           spring.cloud.stream.kafka.binder.brokers                             Kafka服务节点 (默认localhost)
           spring.cloud.stream.kafka.binder.defaultBrokerPort                   Kafka端口 (默认9092)
           spring.cloud.stream.kafka.binder.zkNodes                             ZooKeeper服务节点 (默认localhost)
           zspring.cloud.stream.kafka.binder.defaultZkPort                      ZooKeeper端口 (默认2181)

   -) '注意'
       如果项目中同时使用了'rabbit + kafka'的话, 那么就要给bus声明使用'哪个小妾侍寝'
       spring:
         cloud:
           stream:
             default-binder: rabbit



5->集成————Config_Server_Bus
   -) 'pom'
       <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-config-server</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bus-amqp</artifactId>
            </dependency>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>

        </dependencies>

   -) '配置文件 - bootstrap.yml'
       spring:
         rabbitmq:
           host: localhost
           port: 5672
           username: guest
           password: guest


6->集成————Config_Client_Bus
   -) 'pom'
       <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-config</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bus-amqp</artifactId>
            </dependency>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>

        </dependencies>

   -) '配置文件 - bootstrap.yml'
       spring:
         rabbitmq:
           host: localhost
           port: 5672
           username: guest
           password: guest



7->好搭档(pom)
                          +-----------------------------------------------------------------------+
                          |        'config-server'      |           |      'config-client'        |
                          |  spring-cloud-config-server |           | spring-cloud-starter-config |
                          |                             |           |   spring-boot-starter-web   |
                          +-----------------------------------+-----------------------------------+
                                                              |
                                              +---------------+--------------+
                                              |          'actuator'          |
                                              | spring-boot-starter-actuator |
                                              +---------------+--------------+
                                                              |
                                      +-----------------------+--------------------+
                                      |                     'bus'                  |
                                      |       spring-cloud-starter-bus-amqp        |
                                      |       spring-cloud-starter-bus-kafka       |
                                      +--------------------------------------------+
